package com.yuanrui.practice.server.service.impl;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
import com.yuanrui.practice.api.common.PageInfo;
import com.yuanrui.practice.api.common.PageResult;
import com.yuanrui.practice.api.req.*;
import com.yuanrui.practice.api.vo.*;
import com.yuanrui.practice.server.dao.*;
import com.yuanrui.practice.server.entity.dto.*;
import com.yuanrui.practice.server.entity.po.*;
import com.yuanrui.practice.server.enums.AnswerStatusEnum;
import com.yuanrui.practice.server.enums.CompleteStatusEnum;
import com.yuanrui.practice.server.enums.IsDeletedFlagEnum;
import com.yuanrui.practice.server.enums.SubjectInfoTypeEnum;
import com.yuanrui.practice.server.rpc.UserRpc;
import com.yuanrui.practice.server.service.PracticeDetailService;
import com.yuanrui.practice.server.subject.SubjectTypeHandlerFactory;
import com.yuanrui.practice.server.util.DateUtils;
import com.yuanrui.practice.server.util.LoginUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.*;
import java.util.stream.Collectors;

@Service
@Slf4j
public class PracticeDetailServiceImpl implements PracticeDetailService {

    @Resource
    private PracticeDetailDao practiceDetailDao;
    @Resource
    private PracticeSetDetailDao practiceSetDetailDao;
    @Resource
    private PracticeDao practiceDao;

    @Resource
    private PracticeSetDao practiceSetDao;
    @Resource
    private SubjectInfoDao subjectInfoDao;

    @Resource
    private SubjectTypeHandlerFactory subjectTypeHandlerFactory;
    @Resource
    private SubjectJudgeDao subjectJudgeDao;

    @Resource
    private SubjectMappingDao subjectMappingDao;
    @Resource
    private SubjectLabelDao subjectLabelDao;
    @Resource
    private UserRpc userRpc;

    public PracticeDetailServiceImpl(PracticeDetailDao practiceDetailDao, PracticeSetDetailDao practiceSetDetailDao, PracticeDao practiceDao, PracticeSetDao practiceSetDao, SubjectInfoDao subjectInfoDao) {
        this.practiceDetailDao = practiceDetailDao;
        this.practiceSetDetailDao = practiceSetDetailDao;
        this.practiceDao = practiceDao;
        this.practiceSetDao = practiceSetDao;
        this.subjectInfoDao = subjectInfoDao;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean submit(SubmitPracticeDetailReq req) {
        PracticePO practicePO = new PracticePO();
        Long practiceId = req.getPracticeId();
        Long setId = req.getSetId();
        practicePO.setSetId(setId);
        String TimeUse = req.getTimeUse();
        String hours = TimeUse.substring(0, 2);
        String minutes = TimeUse.substring(2, 4);
        String seconds = TimeUse.substring(4, 6);
        practicePO.setTimeUse(hours + ":" + minutes + ":" + seconds);
        practicePO.setSubmitTime(DateUtils.stringToDate(req.getSubmitTime()));
        practicePO.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        practicePO.setCreatedBy(LoginUtil.getLoginId());
        practicePO.setCompleteStatus(CompleteStatusEnum.FINISHED.getCode());
        practicePO.setCreatedTime(new Date());

        //计算正确率
        Integer correctCount = practiceDetailDao.selectCorrectCount(practiceId);
        List<PracticeSetDetailPO> practiceSetDetailPOS = practiceSetDetailDao.selectBySetId(setId);
        int total = practiceSetDetailPOS.size();
        BigDecimal correctRate = new BigDecimal(correctCount)
                .divide(new BigDecimal(total), 4, BigDecimal.ROUND_HALF_UP)
                .multiply(new BigDecimal(100));
        practicePO.setCorrectRate(correctRate);
        PracticePO po = practiceDao.selectById(practiceId);
        if (Objects.isNull(po)) {
            practiceDao.insert(practicePO);
        } else {
            practicePO.setId(practiceId);
            practiceDao.update(practicePO);
        }

        practiceSetDao.updateHeat(setId);

        //补充剩余题目的记录
        List<PracticeDetailPO> practiceDetailPOList = practiceDetailDao.selectByPracticeId(practiceId);
        List<PracticeSetDetailPO> minusList = practiceSetDetailPOS.stream()
                .filter(item -> !practiceDetailPOList.stream()
                        .map(PracticeDetailPO::getSubjectId)
                        .toList()
                        .contains(item.getSubjectId()))
                .collect(Collectors.toList());
        if (log.isInfoEnabled()) {
            log.info("题目差集{}", JSON.toJSONString(minusList));
        }
        //将未作答的题目插入到练习详情表
        if (CollectionUtils.isNotEmpty(minusList)) {
            minusList.forEach(e -> {
                PracticeDetailPO practiceDetailPO = new PracticeDetailPO();
                practiceDetailPO.setPracticeId(practiceId);
                practiceDetailPO.setSubjectType(e.getSubjectType());
                practiceDetailPO.setSubjectId(e.getSubjectId());
                practiceDetailPO.setAnswerStatus(0);
                practiceDetailPO.setAnswerContent("");
                practiceDetailPO.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
                practiceDetailPO.setCreatedTime(new Date());
                practiceDetailPO.setCreatedBy(LoginUtil.getLoginId());
                practiceDetailDao.insertSingle(practiceDetailPO);
            });
        }
        return true;
    }

    /**
     * 提交题目
     *
     * @param req 提交题目请求
     * @return 是否成功
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Boolean submitSubject(SubmitSubjectDetailReq req) {
        String timeUse = req.getTimeUse();
        if (timeUse.equals("0")) {
            timeUse = "000000";
        }
        String hours = timeUse.substring(0, 2);
        String minutes = timeUse.substring(2, 4);
        String seconds = timeUse.substring(4, 6);
        PracticePO practicePO = new PracticePO();
        practicePO.setId(req.getPracticeId());
        practicePO.setTimeUse(hours + ":" + minutes + ":" + seconds);
        practicePO.setSubmitTime(new Date());
        practiceDao.update(practicePO);

        PracticeDetailPO practiceDetailPO = new PracticeDetailPO();
        practiceDetailPO.setPracticeId(req.getPracticeId());
        practiceDetailPO.setSubjectId(req.getSubjectId());
        String answerContent = "";
        //排序答案
        if (CollectionUtils.isNotEmpty(req.getAnswerContents())) {
            List<Integer> answerContents = req.getAnswerContents();
            Collections.sort(answerContents);
            answerContent = StringUtils.join(answerContents, ",");
        }
        practiceDetailPO.setAnswerContent(answerContent);
        SubjectDTO subjectDTO = new SubjectDTO();
        subjectDTO.setSubjectId(req.getSubjectId());
        subjectDTO.setSubjectType(req.getSubjectType());
        //获取正确答案，并判断答案是否正确
        SubjectDetailDTO subjectDetail = getSubjectDetail(subjectDTO);
        StringBuffer correctAnswer = new StringBuffer();
        if (req.getSubjectType().equals(SubjectInfoTypeEnum.JUDGE.getCode())) {
            Integer isCorrect = subjectDetail.getIsCorrect();
            correctAnswer.append(isCorrect);
        } else {
            subjectDetail.getOptionList().forEach(e -> {
                if (Objects.equals(e.getIsCorrect(), 1)) {
                    correctAnswer.append(e.getOptionType()).append(",");
                }
            });
            if (!correctAnswer.isEmpty()) {
                correctAnswer.deleteCharAt(correctAnswer.length() - 1);
            }
        }
        if (Objects.equals(correctAnswer.toString(), answerContent)) {
            practiceDetailPO.setAnswerStatus(1);
        } else {
            practiceDetailPO.setAnswerStatus(0);
        }
        practiceDetailPO.setIsDeleted(IsDeletedFlagEnum.UN_DELETED.getCode());
        practiceDetailPO.setCreatedBy(LoginUtil.getLoginId());
        practiceDetailPO.setCreatedTime(new Date());
        practiceDetailPO.setSubjectType(req.getSubjectType());
        PracticeDetailPO existDetail = practiceDetailDao.selectDetail(req.getPracticeId(), req.getSubjectId(), LoginUtil.getLoginId());
        if (Objects.isNull(existDetail)) {
            practiceDetailDao.insertSingle(practiceDetailPO);
        } else {
            practiceDetailPO.setId(existDetail.getId());
            practiceDetailDao.update(practiceDetailPO);
        }
        return true;
    }

    @Override
    public List<ScoreDetailVO> getScoreDetail(GetScoreDetailReq req) {
        Long practiceId = req.getPracticeId();
        List<ScoreDetailVO> scoreDetailVOS = new LinkedList<>();
        List<PracticeDetailPO> practiceDetailPOS = practiceDetailDao.selectByPracticeId(practiceId);
        if (CollectionUtils.isEmpty(practiceDetailPOS)) {
            return Collections.emptyList();
        }
        practiceDetailPOS.forEach(practiceDetailPO -> {
            ScoreDetailVO scoreDetailVO = new ScoreDetailVO();
            scoreDetailVO.setSubjectId(practiceDetailPO.getSubjectId());
            scoreDetailVO.setSubjectType(practiceDetailPO.getSubjectType());
            scoreDetailVO.setIsCorrect(practiceDetailPO.getAnswerStatus());
            scoreDetailVOS.add(scoreDetailVO);
        });
        return scoreDetailVOS;
    }

    @Override
    public SubjectDetailVO getSubjectDetail(GetSubjectDetailReq req) {
        SubjectDetailVO subjectDetailVO = new SubjectDetailVO();
        Long subjectId = req.getSubjectId();
        Integer subjectType = req.getSubjectType();
        SubjectDTO subjectDTO = new SubjectDTO();
        subjectDTO.setSubjectType(subjectType);
        subjectDTO.setSubjectId(subjectId);
        //获取答案
        SubjectDetailDTO subjectDetailDTO = getSubjectDetail(subjectDTO);
        List<SubjectOptionDTO> optionList = subjectDetailDTO.getOptionList();
        List<PracticeSubjectOptionVO> optionVOList = new LinkedList<>();
        List<Integer> correctAnswer = new LinkedList<>();
        if (CollectionUtils.isNotEmpty(optionList)) {
            optionList.forEach(option -> {
                PracticeSubjectOptionVO optionVO = new PracticeSubjectOptionVO();
                optionVO.setOptionType(option.getOptionType());
                optionVO.setOptionContent(option.getOptionContent());
                optionVO.setIsCorrect(option.getIsCorrect());
                optionVOList.add(optionVO);
                if (option.getIsCorrect() == 1) {
                    correctAnswer.add(option.getOptionType());
                }
            });
        }
        if (subjectType.equals(SubjectInfoTypeEnum.JUDGE.getCode())) {
            Integer isCorrect = subjectDetailDTO.getIsCorrect();
            PracticeSubjectOptionVO correctOption = new PracticeSubjectOptionVO();
            correctOption.setOptionType(1);
            correctOption.setOptionContent("正确");
            correctOption.setIsCorrect(isCorrect == 1 ? 1 : 0);
            PracticeSubjectOptionVO errorOptionVO = new PracticeSubjectOptionVO();
            errorOptionVO.setOptionType(2);
            errorOptionVO.setOptionContent("错误");
            errorOptionVO.setIsCorrect(isCorrect == 0 ? 1 : 0);
            optionVOList.add(correctOption);
            optionVOList.add(errorOptionVO);
            correctAnswer.add(subjectDetailDTO.getIsCorrect());
        }
        subjectDetailVO.setOptionList(optionVOList);
        subjectDetailVO.setSubjectParse(subjectDetailDTO.getSubjectParse());
        subjectDetailVO.setSubjectName(subjectDetailDTO.getSubjectName());
        subjectDetailVO.setCorrectAnswer(correctAnswer);
        //自己的答题答案
        List<Integer> respondAnswer = new LinkedList<>();
        Long practiceId = req.getPracticeId();
        PracticeDetailPO practiceDetailPO = practiceDetailDao.selectAnswer(practiceId, subjectId);
        String answerContent = practiceDetailPO.getAnswerContent();
        if (StringUtils.isNotBlank(answerContent)) {
            String[] split = answerContent.split(",");
            for (String s : split) {
                respondAnswer.add(Integer.valueOf(s));
            }
        }
        subjectDetailVO.setRespondAnswer(respondAnswer);
        //获取关联标签
        List<SubjectMappingPO> subjectMappingPOList = subjectMappingDao.getLabelIdsBySubjectId(subjectId);
        List<Long> labelIdList = new LinkedList<>();
        subjectMappingPOList.forEach(subjectMappingPO -> {
            labelIdList.add(subjectMappingPO.getLabelId());
        });
        List<String> labelNameList = subjectLabelDao.getLabelNameByIds(labelIdList);
        subjectDetailVO.setLabelNames(labelNameList);
        return subjectDetailVO;
    }

    @Override
    public ReportVO getReport(GetReportReq req) {
        ReportVO reportVO = new ReportVO();
        Long practiceId = req.getPracticeId();
        PracticePO practicePO = practiceDao.selectById(practiceId);
        if (Objects.isNull(practicePO)) {
            return null;
        }
        //获取套卷信息
        PracticeSetPO practiceSetPO = practiceSetDao.queryById(practicePO.getSetId());
        reportVO.setTitle(practiceSetPO.getSetName());

        List<PracticeDetailPO> practiceDetailPOS = practiceDetailDao.selectByPracticeId(practiceId);
        if (CollectionUtils.isEmpty(practiceDetailPOS)) {
            return null;
        }
        int total = practiceDetailPOS.size();
        List<PracticeDetailPO> correctPoList = practiceDetailPOS.stream().filter(e -> Objects.equals(e.getAnswerStatus(), AnswerStatusEnum.CORRECT.getCode())).collect(Collectors.toList());
        reportVO.setCorrectSubject(correctPoList.size() + "/" + total);

        List<ReportSkillVO> reportSkillVOS = new LinkedList<>();
        Map<Long, Integer> totalMap = getSubjectLabelMap(practiceDetailPOS);
        Map<Long, Integer> correctMap = getSubjectLabelMap(correctPoList);
        totalMap.forEach((key, val) -> {
            ReportSkillVO skillVO = new ReportSkillVO();
            SubjectLabelPO labelPO = subjectLabelDao.queryById(key);
            String labelName = labelPO.getLabelName();
            Integer correctCount = correctMap.get(key);
            if (Objects.isNull(correctCount)) {
                correctCount = 0;
            }
            skillVO.setName(labelName);
            BigDecimal rate = BigDecimal.ZERO;
            if (!Objects.equals(val, 0)) {
                rate = new BigDecimal(correctCount.toString()).divide(new BigDecimal(val.toString()), 4,
                        BigDecimal.ROUND_HALF_UP).multiply(new BigDecimal(100));
            }
            skillVO.setStar(rate);
            reportSkillVOS.add(skillVO);
        });
        if (log.isInfoEnabled()) {
            log.info("获取到的正确率{}", JSON.toJSONString(reportSkillVOS));
        }
        reportVO.setSkill(reportSkillVOS);
        return reportVO;
    }

    @Override
    public List<RankVO> getPracticeRankList() {
        //获取练题榜
        List<RankVO> rankVOList = new LinkedList<>();
        List<RankPO> rankPOS = practiceDetailDao.getPracticeCount();
        if (CollectionUtils.isEmpty(rankPOS)) {
            return Collections.emptyList();
        }
        rankPOS.forEach(rankPO -> {
            RankVO rankVO = new RankVO();
            rankVO.setCount(rankPO.getCount());
            UserInfo userInfo = userRpc.getUserInfo(rankPO.getCreatedBy());
            rankVO.setName(userInfo.getNickName());
            rankVO.setAvatar(userInfo.getAvatar());
            rankVOList.add(rankVO);
        });
        return rankVOList;
    }

    @Override
    public PageResult<UnCompletePracticeSetVO> getUnCompletePractice(GetUnCompletePracticeReq req) {
        PageResult<UnCompletePracticeSetVO> pageResult = new PageResult<>();
        PageInfo pageInfo = req.getPageInfo();
        pageResult.setPageNo(pageInfo.getPageNo());
        pageResult.setPageSize(pageInfo.getPageSize());
        int start = (pageInfo.getPageNo() - 1) * pageInfo.getPageSize();
        String loginId = LoginUtil.getLoginId();
        Integer count = practiceDao.getUnCompleteCount(loginId);
        if (count == 0) {
            return pageResult;
        }
        List<PracticePO> poList = practiceDao.getUnCompleteList(loginId, start, req.getPageInfo().getPageSize());
        if (log.isInfoEnabled()) {
            log.info("获取未完成的考卷列表{}", JSON.toJSONString(poList));
        }
        List<UnCompletePracticeSetVO> list = new LinkedList<>();
        poList.forEach(e -> {
            UnCompletePracticeSetVO vo = new UnCompletePracticeSetVO();
            vo.setSetId(e.getSetId());
            vo.setPracticeId(e.getId());
            vo.setPracticeTime(DateUtils.format(e.getCreatedTime(), "yyyy-MM-dd"));
            PracticeSetPO practiceSetPO = practiceSetDao.selectById(e.getSetId());
            String setName=practiceSetPO.getSetName();
            if (setName.isEmpty()){
                return;
            }
            vo.setTitle(setName);
            list.add(vo);
        });
        pageResult.setResult(list);
        pageResult.setTotal(count);
        return pageResult;
    }

    @Override
    @Transactional
    public Boolean giveUpPractice(Long practiceId) {
        practiceDetailDao.deleteByPracticeId(practiceId);
        practiceDao.deleteById(practiceId);
        return true;
    }

    /**
     * 获取题目标签
     * key:标签id,value:题目数量
     *
     * @param practiceDetailPOS 练习详情
     * @return 题目标签
     */
    private Map<Long, Integer> getSubjectLabelMap(List<PracticeDetailPO> practiceDetailPOS) {
        if (CollectionUtils.isEmpty(practiceDetailPOS)) {
            return Collections.emptyMap();
        }
        Map<Long, Integer> map = new HashMap<>();
        practiceDetailPOS.forEach(detail -> {
            Long subjectId = detail.getSubjectId();
            List<SubjectMappingPO> labelIdPO = subjectMappingDao.getLabelIdsBySubjectId(subjectId);
            labelIdPO.forEach(po -> {
                Long labelId = po.getLabelId();
                if (Objects.isNull(map.get(labelId))) {
                    map.put(labelId, 1);
                    return;
                }
                map.put(labelId, map.get(labelId) + 1);
            });
        });
        if (log.isInfoEnabled()) {
            log.info("获取到的题目对应的标签map{}", JSON.toJSONString(map));
        }
        return map;
    }

    private SubjectDetailDTO getSubjectDetail(SubjectDTO subjectDTO) {
        SubjectDetailDTO subjectDetailDTO = new SubjectDetailDTO();
        SubjectPO subjectPO = subjectInfoDao.selectById(subjectDTO.getSubjectId());
        if (Objects.equals(subjectDTO.getSubjectType(), SubjectInfoTypeEnum.JUDGE.getCode())) {
            SubjectJudgePO subjectJudgePO = subjectJudgeDao.selectBySubjectId(subjectPO.getId());
            subjectDetailDTO.setIsCorrect(subjectJudgePO.getIsCorrect());
        }
        List<PracticeSubjectOptionVO> query = subjectTypeHandlerFactory.getHandler(subjectPO.getSubjectType()).query(subjectPO.getId());
        subjectDetailDTO.setSubjectName(subjectPO.getSubjectName());
        subjectDetailDTO.setSubjectParse(subjectPO.getSubjectParse());
        List<SubjectOptionDTO> optionList = new ArrayList<>();
        query.forEach(e -> {
            SubjectOptionDTO subjectOptionDTO = new SubjectOptionDTO();
            subjectOptionDTO.setOptionContent(e.getOptionContent());
            subjectOptionDTO.setOptionType(e.getOptionType());
            subjectOptionDTO.setIsCorrect(e.getIsCorrect());
            optionList.add(subjectOptionDTO);
        });
        subjectDetailDTO.setOptionList(optionList);
        return subjectDetailDTO;
    }
}
